package br.cin.stadium.middleware.network;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.UUID;

import br.cin.stadium.middleware.event.Event;
import br.cin.stadium.middleware.event.EventCallbackReceiver;
import br.cin.stadium.middleware.event.EventManager;
import br.cin.stadium.middleware.event.EventMiddleware;
import br.cin.stadium.middleware.util.Logger;

public class SocketConnection extends Thread implements EventCallbackReceiver {
	
	public static final int SOCKET_FROM_APP = -1;
	
	private Socket socketClient;
	private int socketID;
	
	private InputStream is = null;
	private DataInputStream mdis = null;
	private DataOutputStream mdos = null;
	private OutputStream os = null;
	
	public SocketConnection(Socket connection) {
		this.socketClient = connection;
		this.socketID = UUID.randomUUID().hashCode();
	}
	
	
	/* (non-Javadoc)
	 * @see java.lang.Thread#run()
	 */
	public void run() {
		Logger.log("SocketConnection", "Run", "Begin");

		try {
			
			if(this.socketID == SOCKET_FROM_APP) {
				this.processFromApp();
				
			} else {
				this.processFromNetwork();
			}
			
		} catch (IOException e) {
			Logger.logError("",e);
		} 
		
		Logger.log("SocketConnection", "Run", "End");
	}
	
	/**
	 * @return
	 * @throws IOException
	 */
	public byte[] processFromApp() throws IOException {
		Logger.log("SocketConnection", "processFromApp", "Begin");

		byte[] bytes = null;

		is = this.socketClient.getInputStream();

		int length = 0;
		while (length == 0) {
			length = is.available();
			if (length == 0) {
				continue;
			}
			bytes = new byte[length];
			is.read(bytes);
		}
		
		Logger.log("SocketConnection", "processFromApp", "End (" + bytes.length + ")");
		return bytes;
	}
	
	public void sendDataFromApp(byte[] data) throws NetworkException {
		Logger.log("SocketConnection", "sendDataFromApp", "Begin");

		try {
			this.socketClient.getOutputStream().write(data);
		} catch (IOException e) {
			throw new NetworkException("Error trying to write in OutputStream - socketId = " + this.socketID);
		}
		
		Logger.log("SocketConnection", "sendDataFromApp", "End");
	}
	
	public void processFromNetwork() throws IOException {
		Logger.log("SocketConnection", "processFromNetwork", "Begin");

		is = this.socketClient.getInputStream();
		mdis = new DataInputStream(new BufferStream(is));
		os = this.socketClient.getOutputStream();
		
	    int opCode = mdis.readInt();
		
		String mediaParameter = null;
		EventMiddleware e = null;
		
		Logger.log("", "", "opCode = " + opCode);

		switch (opCode) {
			case Event.OPCODE_READ_FROM_NAME:
			case Event.OPCODE_REQUEST_MEDIA:
				Logger.log("OPCODE_READ_FROM_NAME/OPCODE_REQUEST_MEDIA");

				mediaParameter = mdis.readUTF();
				e = new EventMiddleware(this.socketID, opCode, new String[]{mediaParameter});
				EventManager.getInstance().requestAction(e, this);

				break;
				
			case Event.OPCODE_READ_FROM_SECTOR:
				Logger.log("OPCODE_READ_FROM_SECTOR");

				mediaParameter = Integer.toString(mdis.readInt());
				
				e = new EventMiddleware(this.socketID, opCode, new String[]{mediaParameter});
				EventManager.getInstance().requestAction(e, this);
				
				break;
			case Event.OPCODE_READ_ALL_MEDIA:
				Logger.log("OPCODE_READ_ALL_MEDIA");

				e = new EventMiddleware(this.socketID, opCode, new String[]{mediaParameter});
				EventManager.getInstance().requestAction(e, this);

				break;
				

			case Event.OPCODE_NEW_WRITE:
				Logger.log("OPCODE_NEW_WRITE");
				
				e = new EventMiddleware(this.socketID, opCode);

			    int size = mdis.readInt();

				Logger.log("size = " + size);

				int counter = 0;
				byte[] raw = new byte[size];
				
				
				while(counter < size) {
					int read = is.read(raw, counter, size-counter);
					counter += read;
				}

				e.setData(raw);
				e.setParameter(new String[]{((InetSocketAddress)this.socketClient.getRemoteSocketAddress()).getAddress().getHostAddress()});
				EventManager.getInstance().requestAction(e, this);
				
				this.closeConnection();
				

				break;
			default:
				Logger.log("", "", "opCode not valid = " + opCode);
				break;
			}
		Logger.log("SocketConnection", "processFromNetwork", "End");
	}
	


	public void handleEvent(EventMiddleware event) {
		Logger.log("SocketConnection", "handleEvent", "Begin (" + event.getOpCode() + "," + 
				                                                  event.getParameter() + "," + 
				                                                  event.getData().length + ")");

		try {
			mdos = new DataOutputStream(new BufferStream(os));
			
			switch (event.getOpCode()) {
				case Event.OPCODE_READ_FROM_NAME:
				case Event.OPCODE_READ_FROM_SECTOR:
				case Event.OPCODE_REQUEST_MEDIA:
				case Event.OPCODE_READ_ALL_MEDIA:
					mdos.writeInt(event.getData().length);
					mdos.write(event.getData());

					break;
				default:
					Logger.log("", "", "opCode not valid = " + event.getOpCode());
					break;
			} 
		}catch (Exception e1) {
			e1.printStackTrace();
		} finally {
			this.closeConnection();
		}
				
		Logger.log("SocketConnection", "handleEvent", "End");
	}
	
	public void closeConnection() {
		Logger.log("SocketConnection", "closeConnection", "Begin");

		try {
			if (is != null) {
				is.close();
			}
			if (os != null) {
				os.close();
			}

		} catch (IOException ioe) {}

		try {

			if (this.socketClient != null) {
				this.socketClient.close();
			}
		} catch (IOException ioe) {}

		Logger.log("SocketConnection", "closeConnection", "End");
	}


	public int getSocketID() {
		return socketID;
	}

}
